# -*- coding: utf-8 -*-
"""
Module Description: 各数据源的数据访问调用接口
"""
import abc


class IModelManager(object):
    """提供管理数据的管理接口"""
    __metaclass__ = abc.ABCMeta

    def all(self):
        """
        返回所有数据
        """

    def filter(self, **kwargs):
        """
        查询并返回一个列表

        # eq
        >>> # SELECT * FROM Table WHERE Gold = 10
        >>> objects.filter(Gold=10)
        >>> objects.filter(Gold__eq=10)
        # ne
        >>> # SELECT * FROM Table WHERE Gold != 10
        >>> objects.filter(Gold__ne=10)
        # lt
        >>> # SELECT * FROM Table WHERE Gold < 10
        >>> objects.filter(Gold__lt=10)
        # lte
        >>> # SELECT * FROM Table WHERE Gold <= 10
        >>> objects.filter(Gold__lte=10)
        # gt
        >>> # SELECT * FROM Table WHERE Gold > 10
        >>> objects.filter(Gold__gt=10)
        # gte
        >>> # SELECT * FROM Table WHERE Gold >= 10
        >>> objects.filter(Gold__gte=10)
        # in
        >>> # SELECT * FROM Table WHERE Gold in (10,20,30)
        >>> objects.filter(Gold__in=[10, 20, 30])
        # is
        >>> # SELECT * FROM Table WHERE MayNullStr is null
        >>> objects.filter(MayNullStr__is=None)
        # isnot
        >>> # SELECT * FROM Table WHERE MayNullStr is not null
        >>> objects.filter(MayNullStr__isnot=None)
        # like
        >>> # SELECT * FROM Table WHERE SomeStr like 'Me%'
        >>> objects.filter(SomeStr__like='Me%')
        >>> # SELECT * FROM Table WHERE SomeStr like '%Me'
        >>> objects.filter(SomeStr__like='%Me')
        >>> # SELECT * FROM Table WHERE SomeStr like '%Me%'
        >>> objects.filter(SomeStr__like='%Me%')
        """

    def columns(self, *fields):
        """
        限定查询返回的数据列.
        若尝试获取未在限定列表中的列的值,获取到的是默认值.
        >>> # SELECT * FROM Table WHERE Rid="rid" ==>> SELECT Name FROM Table WHERE Rid="rid"
        >>> objects.columns('Name').get(Rid='rid')
        >>> # SELECT * FROM Table WHERE Rid="rid" ==>> SELECT Name, Uid, Lv, SrvId FROM Table WHERE Rid="rid"
        >>> objects.columns('Name', 'Uid', 'Lv', 'SrvId').filter(Rid='rid')
        >>> objects.columns(*['Name', 'Uid', 'Lv', 'SrvId']).filter(Rid='rid')
        """

    def get(self, **kwargs):
        """
        查询并返回一个记录
        """

    def count(self, **kwargs):
        """
        返回查询结果集的记录数量
        >>> count()
        返回表的记录数
        >>> count(**kwargs)
        返回符合条件的记录数
        """

    def max(self, field_name):
        """
        返回指定字段当前的最大值

        >>> dal.max('Gold')
        >>> dal.column().filter().max('Gold')
        """

    def min(self, field_name):
        """
        返回指定字段当前的最小值

        >>> dal.min('Gold')
        >>> dal.column().filter().min('Gold')
        """

    def order(self, *fields):
        """
        排序的字段
        :param fields: 排序的字段列表，带'-'前缀则为降序，否则为升序
        :return:
        """

    def create(self, **kwargs):
        """
        创建一条记录并返回

        传入的参数可以不是主键，但若主键字段冲突/唯一索引字段冲突，则 会立刻报错

        :note 不论是否被事务包含,create会直接提交
        """

    def create(self, **kwargs):
        """
        创建一条记录并返回

        传入的参数可以不是主键，但若主键字段冲突/唯一索引字段冲突，则 不会立刻报错

        :note 若被事务包含,create会随事务提交
        """

    def get_or_create(self, **kwargs):
        """
        查询是否存在，若不存在则创建一条新记录

        注意查询条件需要能够唯一指定一个记录，即查询条件的参数字段创建后不能发生改变
        :return record, is_new
        """

    def insert_many(self, data_list):
        """
        批量插入记录

        >>> data_list = [{'field1': 'val1-1', 'field2': 'val1-2'}, {'field1': 'val2-1', 'field2': 'val2-2'}]
        >>> objects.insert_many(data_list)
        :param data_list: 新纪录的字段数据列表
        :return:
        """

    def update(self, **kwargs):
        """
        更新符合条件的记录

        若不传入筛选条件，为了避免错误更新全表数据，会报错
        >>> objects.filter(X=0).update(X=3)   # 正常
        >>> objects.update(X=3)                    # 报错
        >>> objects.filter().update(X=3)         # 报错
        """

    def update_force(self, **kwargs):
        """
        更新符合条件的记录，只在需要更新全表数据时使用
        >>> objects.update_force(X=3)   # 正常，更新所有数据
        >>> objects.filter().update_force(X=3) # 正常，更新所有数据
        """

    def delete(self):
        """
        删除符合条件的记录

        若不传入筛选条件，为了避免错误删除全表数据，会报错
        >>> objects.filter(X=0).delete()   # 正常
        >>> objects.delete()                    # 报错
        >>> objects.filter().delete()         # 报错
        """

    def delete_force(self):
        """
        删除符合条件的记录，只在需要删除全表数据时使用
        >>> objects.delete_force()   # 正常，删除所有数据
        >>> objects.filter().delete_force() # 正常，删除所有数据
        """

    def sql_query(self, sql):
        """
        执行查询sql语句,返回[{record}, ...],直接在db执行,不保证数据是最新的
        >>> objects.sql_query('SELECT * FROM TbRole') # 正常,返回TbRole所有数据
        >>> objects.sql_query('INSERT INTO TbItem VALUES("guid", "1", "1", "3")') # 报错,不能执行INSERT语句
        >>> objects.sql_query('UPDATE TbRole SET Sex=0') # 报错,不能执行UPDATE语句
        >>> objects.sql_query('DELETE FROM TbTitle') # 报错,不能执行DELETE语句
        """


class PeeweeDALDescriptor(object):
    """
    peewee数据访问管理类描述器
    """

    def __get__(self, instance, owner):
        return PeeweeDALManager(owner.model_class, owner)


class PeeweeDALManager(IModelManager):
    """管理peewee数据请求"""

    def __init__(self, model_class, po_class):
        import peewee
        assert issubclass(model_class, peewee.Model)
        self.model_class = model_class
        self.po_class = po_class

        # 过滤条件
        self._filter = {}
        # 查询字段
        self._columns = []
        # 条件连接符，只支持一种类型
        self._operator = 'AND'
        # 排序字段
        self._order = []
        # 限制记录数
        self._limit = None
        # 偏移量
        self._offset = 0

    #################
    # 查询方法（链式调用）
    #################

    def columns(self, *fields):
        """
        返回的数据列
        :param fields:
        """
        if self._columns:
            raise ValueError("Don't call columns() twice")

        for field in fields:
            if field not in self._columns:
                self._columns.append(field)
        return self

    def _all(self):
        return self.model_class.select()

    def filter(self, **kwargs):
        """
        查询并返回一个列表
        """
        cdt = {}
        for k, v in kwargs.iteritems():
            if '__' not in k:
                # 默认比较相等
                k += '__eq'
            cdt[k] = v

        self._filter.update(cdt)
        return self

    def order(self, *fields):
        """
        排序的字段
        :param fields:
        :return:
        """
        if self._order:
            # 因为排序字段的次序有意义，所以不允许调用多次，一次性定义好
            raise ValueError("Don't call order() twice")

        self._order = list(fields)
        return self

    def limit(self, limit, offset=0):
        """
        限制记录条数
        :param limit:
        :param offset:
        :return:
        """
        assert limit > 0 and offset >= 0

        self._limit = limit
        self._offset = offset
        return self

    #################
    # 具体操作方法（立即执行操作并返回结果）
    #################

    def all(self):
        """
        返回所有数据
        """
        return [self.po_class(data) for data in self._all()]

    def get(self, **kwargs):
        """
        查询并返回一个记录
        """
        record_set = self.filter(**kwargs).limit(1)._select_query()
        if record_set and len(record_set) > 0:
            return self.po_class(record_set[0])

    def count(self):
        """
        返回查询结果集的记录数量
        >>> objects.filter().count()
        """
        import peewee

        # 获得count时,不能设置offset
        assert self._offset == 0

        result = self._select_query()
        return result.aggregate(peewee.fn.Count('*'))

    def max(self, field_name):
        if self._columns and field_name not in self._columns:
            # 调用max前用column
            raise ValueError("Field(%s) not in result set!" % field_name)

        import peewee
        result = self._select_query()
        return result.aggregate(peewee.fn.Max(getattr(self.model_class, field_name)))

    def min(self, field_name):
        if self._columns and field_name not in self._columns:
            # 调用min前用column
            raise ValueError("Field(%s) not in result set!" % field_name)

        import peewee
        result = self._select_query()
        return result.aggregate(peewee.fn.Min(getattr(self.model_class, field_name)))

    def create(self, **kwargs):
        with PeeweeTransaction(propagation=TransactionPropagation.REQUIRES_NEW):
            data = self.model_class.create(**kwargs)
        return data if data is None else self.po_class(data)

    def get_or_create(self, **kwargs):
        po_obj = self.get(**kwargs)
        if po_obj:
            return po_obj, False

        po_obj = self.create(**kwargs)
        return po_obj, True

    def insert_many(self, data_list):
        if not data_list:
            return
        self.model_class.insert_many(data_list).execute()

    def update(self, **kwargs):
        if not self._filter:
            raise ValueError("Can't update table without filter condition.Try use update_force")

        return self._do_update(**kwargs)

    def update_force(self, **kwargs):
        return self._do_update(**kwargs)

    def delete(self):
        if not self._filter:
            raise ValueError("Can't delete table without filter condition.Try use delete_force")

        return self._do_delete()

    def delete_force(self):
        return self._do_delete()

    def _expressions(self):
        """
        构造查询筛选参数列表
        """
        import peewee

        expressions = []
        for field_opt, val in self._filter.iteritems():
            field, opt = field_opt.rsplit('__')

            # 如果in的元素是空集,则返回None
            if opt == 'in' and not val:
                return

            # 支持eq, lt, lte, gt, gte, ne, in, is, isnot, like
            pw_opt = peewee.DJANGO_MAP.get(opt)
            # 增加isnot支持
            if opt == 'isnot':
                pw_opt = peewee.OP.IS_NOT
            if not pw_opt \
                    or pw_opt in ['ilike', 'regexp']:  # 不支持的方法
                raise NotImplementedError('Unsupported filter condition:(%s)' % opt)

            exp = peewee.Expression(
                lhs=getattr(self.model_class, field),
                op=pw_opt,
                rhs=val
            )
            expressions.append(exp)
        return expressions

    def _select_query(self):
        """
        构造peewee.SelectQuery对象并返回
        """

        # 筛选列
        clns = [getattr(self.model_class, cln) for cln in self._columns]
        select_obj = self.model_class.select(*clns)

        # 排序
        if self._order:
            order_clauses = []
            for order_field in self._order:
                # ASC field
                if order_field[0] != '-':
                    clause = getattr(self.model_class, order_field).asc()
                # DESC field
                else:
                    clause = getattr(self.model_class, order_field[1:]).desc()
                order_clauses.append(clause)

            select_obj = select_obj.order_by(*order_clauses)

        # limit, offset
        if self._limit:
            select_obj = select_obj.limit(self._limit)
        if self._offset:
            select_obj = select_obj.offset(self._offset)

        # 查询条件
        expressions = self._expressions()

        if expressions is None:
            return []

        return select_obj if not expressions else select_obj.where(*expressions)

    def _update_query(self, **kwargs):
        """
        构造peewee.UpdateQuery对象并返回
        """
        return self.model_class.update(**kwargs)

    def _delete_query(self):
        """
        构造peewee.DeleteQuery对象并返回
        """
        return self.model_class.delete()

    def _do_update(self, **kwargs):
        update_obj = self._update_query(**kwargs)
        expressions = self._expressions()
        if expressions is None:
            return
        if not expressions:
            return update_obj.execute()
        else:
            return update_obj.where(*expressions).execute()

    def _do_delete(self):
        delete_obj = self._delete_query()
        expressions = self._expressions()
        if expressions is None:
            return
        if not expressions:
            return delete_obj.execute()
        else:
            return delete_obj.where(*expressions).execute()

    def sql_query(self, sql):
        from pymysql.cursors import DictCursor
        db = self.model_class._meta.database
        conn = db.get_conn()
        with conn.cursor(DictCursor) as cursor:
            cursor.execute(sql)
            result = cursor.fetchall()
            return result

    #################
    # MAGIC METHODS #
    #################
    def __len__(self):
        """支持len()"""
        return len(self._record_set)

    def __iter__(self):
        """支持遍历"""
        for po in self._record_set:
            yield po

    def __getitem__(self, index):
        """支持分片slice"""
        return self._record_set[index]

    @property
    def _record_set(self):
        """缓存的结果集"""
        if hasattr(self, '_cache_rs'):
            return self._cache_rs

        # 执行查询并缓存
        record_set = self._select_query()
        po_set = []
        if record_set:
            po_set = [self.po_class(record) for record in record_set]
        self._cache_rs = po_set
        return self._cache_rs


########################################
#   ↓事务相关实现↓
########################################
class TransactionPropagation(object):
    """
    事务传播规则类型(参考Spring框架)

    REQUIRED: 若当前有事务,则添加到该事务,否则新建一个事务
    REQUIRES_NEW: 不论当前是否有事务,都新建一个事务.事务处理结果不影响外部事务.
    NOT_SUPPORTED: 不论当前是否有事务,内部操作都直接提交
    """
    REQUIRED = 1
    REQUIRES_NEW = 2
    NOT_SUPPORTED = 3


class TransactionDelegator(object):
    """具体事务类委托对象"""
    transaction_class = None

    @classmethod
    def set_transaction_class(cls, concrete_transaction_class):
        """设置具体事务类,进程内只能设置一次"""
        cls.transaction_class = concrete_transaction_class

    def __new__(cls, *args, **kwargs):
        return cls.transaction_class(*args, **kwargs)


class ITransaction(object):
    """
    提供事务类通用接口

    # with方式调用事务: with块内异常不会提交事务
    >>> with ITransaction() as t:
    >>>     # do something...
    >>> t.is_success()  # 事务是否成功

    # 一般方式调用事务:
    >>> t = ITransaction()
    >>> t.begin()
    >>> # do something...
    >>> t.commit()
    >>> t.is_success()  # 事务是否成功
    """
    __metaclass__ = abc.ABCMeta

    def __init__(self, reraise_error=True, begin=False, propagation=TransactionPropagation.REQUIRED):
        """
        :param reraise_error: 是否直接抛出事务异常(默认True)
        :param begin: 是否直接开启事务(默认True)
        :param propagation: 事务传播规则
        """
        #############################
        # 事务传播规则
        # 值域=TransactionPropagation
        #############################
        self.propagation = propagation

        # 是否直接抛出事务异常
        self._do_reraise_error = reraise_error

        # 是否要直接打开事务
        self._begin = begin

        # 是否已经打开事务
        self._after_begin = False

        # 是否已提交
        self._already_commit = False

        # 是否已回滚
        self._already_rollback = False

        #############################
        # 使用context manager风格的事务写法能够调用的方法与一般风格的不同
        # 需要在事务对象处于不同状态时对能够进行的操作(主要是begin,commit和rollback)进行限定
        # context manager风格: 不允许begin,commit方法的显式调用
        # 一般风格: 不允许__enter__,__exit__的调用
        #############################
        # 是否使用context manager(资源管理器)风格的事务写法
        # 在_before_begin()中设为False
        # 在_before_enter()中设为True
        self._use_ctx_mgr_style = None

        #############################
        # 在事务 未提交/回滚/事务提交失败 的情况下,该函数不会执行
        #############################
        # 在事务成功提交后执行的函数体
        self._func_on_commit = None
        # 提交后执行的函数体参数
        self._func_on_commit_args = None
        self._func_on_commit_kwargs = None

    def _before_begin(self):
        """打开事务前的判断"""
        if self.propagation is not TransactionPropagation.REQUIRED:
            raise ValueError('Current Propagation Not Support begin()')
        if self._use_ctx_mgr_style is True:
            raise RuntimeError('Not supported operation!')
        self._use_ctx_mgr_style = False

    def begin(self):
        """打开事务"""

    def _before_commit(self):
        """提交事务前的判断"""
        if self._use_ctx_mgr_style is True:
            raise RuntimeError('Not supported operation!')
        if self._already_rollback:
            raise RuntimeError('Current transaction already rollback!Can not commit!')
        if self.propagation is not TransactionPropagation.REQUIRED:
            raise ValueError('Current Propagation Not Support commit()')
        self._already_commit = True

    def commit(self):
        """
        提交事务
        :rtype bool
        :return 事务是否成功提交
        """

    def _before_rollback(self):
        """回滚事务前的判断"""
        if self._already_commit:
            raise RuntimeError('Current transaction already commit!Can not rollback!')
        if self._already_rollback:
            raise RuntimeError('Can not rollback transaction many times!')

        self._already_rollback = True

    def rollback(self):
        """
        回滚事务
        """

    def _before_enter(self):
        """进入事务前的检验"""
        if self._use_ctx_mgr_style is False:
            raise RuntimeError('Not supported operation!')
        self._use_ctx_mgr_style = True

    def __enter__(self):
        """进入事务"""

    def _before_exit(self):
        """离开事务前的检验"""
        if self._use_ctx_mgr_style is False:
            raise RuntimeError('Not supported operation!')

        if not self._already_rollback:
            self._already_commit = True

    def __exit__(self, exc_type, exc_val, exc_tb):
        """离开并提交事务"""

    def _after_success_commit(self):
        """事务成功提交后的处理"""
        if not self._already_commit or self._already_rollback:
            return

        if not self.is_success():
            return

        # 执行成功提交后的处理函数
        if self._func_on_commit is not None:
            self._func_on_commit(*self._func_on_commit_args, **self._func_on_commit_kwargs)

    def is_success(self):
        """
        检查事务是否成功提交
        :rtype bool
        :return 事务是否成功提交
        """

    def is_reraise_error(self):
        """
        :rtype bool
        :return: 是否在事务执行失败时抛出异常
        """
        return self._do_reraise_error

    def on_commit(self, func, *args, **kwargs):
        """
        注册事务成功提交后的处理函数
        :param func: 成功提交后的处理函数
        :param args: 参数
        :param kwargs: 参数
        :return:
        """
        if not callable(func):
            raise ValueError('Function on commit must callable!')

        if self._func_on_commit is not None:
            raise ValueError('Function on commit already set!')

        self._func_on_commit = func
        self._func_on_commit_args = args
        self._func_on_commit_kwargs = kwargs


class PeeweeTransaction(ITransaction):
    """
    Peewee事务类
    http://docs.peewee-orm.com/en/latest/peewee/transactions.html
    """

    def __init__(self, reraise_error=True, begin=False, propagation=TransactionPropagation.REQUIRED):
        super(PeeweeTransaction, self).__init__(reraise_error=reraise_error,
                                                begin=begin,
                                                propagation=propagation)

        from framework.dal.models import get_database_pool
        self._database = get_database_pool()

        self._transaction = None
        if self.propagation is TransactionPropagation.REQUIRED:
            self._transaction = self._database.transaction()
        elif self.propagation is TransactionPropagation.REQUIRES_NEW:
            self._transaction = self._database.atomic()
        else:
            raise ValueError('Unsupported Transaction Propagation!')

        self._success = False

        if self._begin:
            self.begin()

    def is_success(self):
        return self._success

    def begin(self):
        self._before_begin()
        self._transaction.db.begin()
        self._after_begin = True

    def commit(self):
        self._before_commit()
        try:
            self._transaction.db.commit()
            self._success = True

            self._after_success_commit()
            return True
        except Exception as e:
            if self.is_reraise_error():
                raise e
            else:
                from utils.traceback_utils import print_exc
                print_exc(e)
            return False

    def rollback(self):
        self._before_rollback()

        if self.propagation is TransactionPropagation.REQUIRES_NEW:
            self._transaction._helper.rollback()
        else:
            self._transaction.rollback()

    def __enter__(self):
        self._before_enter()
        self._transaction.__enter__()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._before_exit()

        try:
            self._transaction.__exit__(exc_type, exc_val, exc_tb)

            # with块内程序无异常且事务没有主动回滚,事务才成功
            if exc_type is None and not self._already_rollback:
                self._success = True

                self._after_success_commit()

        except Exception as e:
            if self.is_reraise_error():
                raise e
            else:
                from utils.traceback_utils import print_exc
                print_exc(e)
